package org.kanglg.demo.service;

import com.google.common.collect.Maps;
import org.apache.commons.lang.StringUtils;
import org.kanglg.common.annotation.Left;
import org.kanglg.common.annotation.NodeID;
import org.kanglg.common.annotation.Right;
import org.kanglg.common.annotation.UpID;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.lang.reflect.*;
import java.util.Map;

public class MpttBS<T> {
    @PersistenceContext
    protected EntityManager em;
    private Field left, right, nodeId, upId;
    private String className, leftName, rightName, nodeIdName, upIdName;
    private Class clazz;
    private Map<String, Method> methods;

    public MpttBS() {
        Class clazz = getClass();
        while (clazz != Object.class) {
            Type t = clazz.getGenericSuperclass();
            if (t instanceof ParameterizedType) {
                Type[] args = ((ParameterizedType) t).getActualTypeArguments();
                if (args[0] instanceof Class) {
                    this.clazz = (Class<T>) args[0];
                    break;
                }
            }
            clazz = clazz.getSuperclass();
        }
        methods = Maps.newHashMap();
        this.className = this.clazz.getSimpleName();
        Field[] fields = this.clazz.getDeclaredFields();
        try {
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                String fieldName = field.getName();
                String getMethodName = "get" + StringUtils.capitalize(fieldName);
                String setMethodName = "set" + StringUtils.capitalize(fieldName);
                if (field.isAnnotationPresent(Left.class)) {
                    Method getMethod = this.clazz.getMethod(getMethodName);
                    Method setMethod = this.clazz.getMethod(setMethodName, field.getType());
                    if (getMethod != null) {
                        methods.put("getLeft", getMethod);
                    }
                    if (setMethod != null) {
                        methods.put("setLeft", setMethod);
                    }
                    left = field;
                    leftName = field.getName();
                }
                if (field.isAnnotationPresent(Right.class)) {
                    Method getMethod = this.clazz.getMethod(getMethodName);
                    Method setMethod = this.clazz.getMethod(setMethodName, field.getType());
                    if (getMethod != null) {
                        methods.put("getRight", getMethod);
                    }
                    if (setMethod != null) {
                        methods.put("setRight", setMethod);
                    }
                    right = field;
                    rightName = field.getName();
                }
                if (field.isAnnotationPresent(NodeID.class)) {
                    Method getMethod = this.clazz.getMethod(getMethodName);
                    Method setMethod = this.clazz.getMethod(setMethodName, field.getType());
                    if (getMethod != null) {
                        methods.put("getNodeId", getMethod);
                    }
                    if (setMethod != null) {
                        methods.put("setNodeId", setMethod);
                    }
                    nodeId = field;
                    nodeIdName = field.getName();
                }
                if (field.isAnnotationPresent(UpID.class)) {
                    Method getMethod = this.clazz.getMethod(getMethodName);
                    Method setMethod = this.clazz.getMethod(setMethodName, field.getType());
                    if (getMethod != null) {
                        methods.put("getUpId", getMethod);
                    }
                    if (setMethod != null) {
                        methods.put("setUpId", setMethod);
                    }
                    upId = field;
                    upIdName = field.getName();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 添加一个节点
    @Transactional
    public void addNode(T p, T node) {
        StringBuffer jql = new StringBuffer(1000);
        Query query;
        Object pId;
        Long pRight;
        if (p == null) {
            jql.append("select max(").append(rightName).append(") from")
                    .append(className).append(" where ").append(upIdName).append("=:zero");
            query = em.createQuery(jql.toString());
            query.setParameter("zero", "0");
            pRight = (Long) query.getSingleResult();
            pId = "0";
        } else {
            pRight = invokeGetMethod("getRight", p);
            pId = invokeGetMethod("getNodeId", p);
        }
        invokeSetMethod("setUpId", node, pId);
        invokeSetMethod("setLeft", node, pRight);
        invokeSetMethod("setRight", node, pRight + 1);
        jql.setLength(0);
        // 更新右坐标
        jql.append("update ").append(className).append(" set ")
                .append(rightName).append("=").append(rightName)
                .append("+2 where ").append(rightName).append(">=:left");
        query = em.createQuery(jql.toString());
        query.setParameter("left", pRight);
        query.executeUpdate();
        jql.setLength(0);
        // 更新左坐标，为插入腾出地方
        jql.append("update ").append(className).append(" set ")
                .append(leftName).append("=").append(leftName)
                .append("+2 where ").append(leftName).append(">:left");
        query = em.createQuery(jql.toString());
        query.setParameter("left", pRight);
        query.executeUpdate();
        em.persist(node);
    }

    // 级联删除一个节点
    public void deleteNodeCascade(T node) {
    }

    // 删除一个节点，保留其子节点
    public void deleteNode(T node) {
    }

    // 修改一个节点的上级
    public void updatePid(T node, T p) {
    }

    // 查询子集
    public void findChild(T node) {
    }

    // 查询子孙集
    public void findAllChild(T node) {
    }

    // 查询路径
    public void findPath(T node) {
    }

    private <X> X invokeGetMethod(String methodName, T node) {
        Method method = methods.get(methodName);
        if (method != null) {
            try {
                return (X) method.invoke(node);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private void invokeSetMethod(String methodName, T node, Object val) {
        Method method = methods.get(methodName);
        if (method != null) {
            try {
                method.invoke(node, val);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}
